package spinal_loongarch_core132

import spinal.core._
import spinal.lib._

object ICacheConfig extends CacheConfig{
    val w_idx = 8
    val w_pos = 2
    val n_way = 2
    val withBitMask = false
    val withDirty = false
}
class IMiss extends Bundle with IMasterSlave{
    val line   :Bool = Bool()
    val arvalid:Bool = Bool()
    val arready:Bool = Bool()
    val araddr :UInt = LISA.PA
    val rvalid :Bool = Bool()
    val rready :Bool = Bool()
    val rlast  :Bool = Bool()
    val rdata  :UInt = UInt(32 bits)
    override def asMaster():Unit = {
        out(arvalid,rready,line,araddr)
        in (arready,rvalid,rlast,rdata)
    }
}
class InstReq extends CacheReq
class InstResp extends CacheResp{
    def inst  :UInt = data
}
class ICacheCtrl extends Area{
    object IState extends SpinalEnum{
        val sIdle,sLookup,sMiss,sOperate = newElement()
    }
    import IState._
    // input fields
    val cancel     = Bool()
    val req_valid  = Bool()
    val miss_ready = Bool()
    val resp_ready = Bool()
    val recv_done  = Bool()
    val fill_done  = Bool()
    val cached     = Bool()
    val ex         = Bool()
    val hit        = Bool()

    val cop_valid  = Bool()
    val cop_ready  = Bool()
    val req_st_0_tag = Bool()
    val req_hit_inv = Bool()
    val start_hit_inv = cop_ready && req_hit_inv
    val st_0_tag   = cop_ready && req_st_0_tag
    val do_hit_inv = RegNext(start_hit_inv)
    val doing_cop  = do_hit_inv || req_hit_inv || req_st_0_tag

    val init = RamInit(8)
    val s  = Reg(IState()) init sIdle
    val addr_ok = Bool()
    val data_ok = Bool()
    val miss_valid = Bool()
    val send_miss  = miss_valid && miss_ready
    val quitable  = Bool()
    val read = Bool()

    def acceptReq:Unit = {
        addr_ok  := !init.valid && !doing_cop
        cop_ready := !do_hit_inv
    }
    def sendMiss:Unit = {
        miss_valid := !cancel
    }
    addr_ok := False
    data_ok := False
    miss_valid := False
    quitable := True
    cop_ready := False
    switch(s){
        is(sIdle){
            acceptReq
        }
        is(sLookup){
            when(hit && cached || ex){
                data_ok := True
                when(resp_ready){
                    acceptReq
                }
            }.otherwise{
                sendMiss
            }
        }
        is(sMiss){
            quitable := False
            when(fill_done && recv_done){
                acceptReq
            }
        }
    }
    read := False
    when(quitable && cancel || init.valid){
        s := sIdle
    }.elsewhen(addr_ok){
        when(req_valid){
            read := True
            s := sLookup
        }.elsewhen(s =/= sIdle){
            s := sIdle
        }
    }.elsewhen(send_miss){
        s := sMiss
    }.elsewhen(start_hit_inv){
        read := True
    }

    def collect(miss:IMiss):Unit = {
        miss_ready := miss.arready
        miss.arvalid := miss_valid
    }
    def collect(req:InstReq):Unit = {
        req_valid := req.valid
        req.allow := addr_ok
    }
    def collect(resp:InstResp):Unit = {
        cancel := resp.cancel
        resp_ready := resp.allow
    }
    def collect(recv:BusReceiver):Unit = {
        recv_done := recv.done
        recv.start := send_miss
    }
    def collect(fill:CacheFiller):Unit = {
        fill_done := fill.done
        fill.start := send_miss && cached
    }
    def collect(info:ILookupInfo):Unit = {
        ex     := info.ex
        cached := info.cached
    }
    def collect(icop:CacopBus):Unit = {
        icop.ready := cop_ready
        cop_valid := icop.valid
        req_st_0_tag := icop.valid && (icop.isIdxInv || icop.isSt0Tag) && icop.isICache
        req_hit_inv  := icop.valid && icop.isHitInv && icop.isICache
    }
}
class ILookupInfo extends LookupInfo{
    val cfg  = ICacheConfig
    val pt   :UInt = Reg(UInt(LISA.w_ppn bits))
    val mat  :UInt = Reg(UInt(2 bits))
    val ex   :Bool = Reg(Bool()      )
    val ecode:UInt = Reg(LISA.Ex.Code)
    val vidx :UInt = Reg(UInt(12 bits))
    def forceCached:Boolean = false
    val cached:Bool = if(forceCached) True else mat === U(1)
    def setFields(miss:IMiss):Unit = {
        miss.araddr := pa
        miss.line   := cached
    }
}
class ICache extends Component{
    val io = new Bundle{
        val ireq  :InstReq  = slave (new InstReq )
        val iresp :InstResp = master(new InstResp)
        val iexcpt:IExcept  = out   (new IExcept )
        val isrch :ITlbSrch = master(new ITlbSrch)
        val imiss :IMiss    = master(new IMiss   )
        val icop  :CacopBus = slave (new CacopBus)
    }

    io.isrch.va := io.ireq.va
    val cfg  = ICacheConfig
    val tagv = cfg.getTagV
    val data = cfg.getIDat
    val ctrl = new ICacheCtrl
    val info = new ILookupInfo
    val repl = new LRUReplace
    val recv = new BusReceiver(io.iresp,io.imiss)
    val (hits,hit) = info.tagCompare(tagv)
    val rdat = cfg.getVec2D(data(_)(_).io.rd)
    val inst = info.select(hits,rdat)
    val fill = new CacheFiller(io.imiss)
    info.va := io.ireq.va
    when(ctrl.read){
        info.assignSomeByName(io.isrch.res)
        info.vidx := info.nextVIdx
    }
    ctrl.collect(io.ireq)
    ctrl.collect(io.iresp)
    ctrl.collect(io.imiss)
    ctrl.collect(info)
    ctrl.collect(recv)
    ctrl.collect(fill)
    ctrl.collect(io.icop)
    ctrl.hit := hit
    info.setFields(io.imiss)
    io.iexcpt.ex    := ctrl.data_ok && info.ex
    info.send_ex(io.iexcpt)
    io.iresp.valid  := recv.valid || ctrl.data_ok
    io.iresp.data   := Mux(recv.valid,recv.data,inst)

    val addr = UInt(8 bits)
    when(ctrl.start_hit_inv){
        info.vidx := io.icop.vidx
        info.pt   := io.icop.ptag
    }
    when(ctrl.init.valid){
        addr := ctrl.init.idx
    }.elsewhen(fill.valid || ctrl.do_hit_inv){
        addr := info.widx
    }.elsewhen(ctrl.req_st_0_tag || ctrl.req_hit_inv){
        addr := io.icop.vidx(11 downto 4)
    }.otherwise{
        addr := info.ridx
    }
    val st_0_mask = cfg.getWayMask(io.icop.vidx(0 downto 0))
    val fill_tagv = fill.isWriteTagV(io.imiss)
    val fill_data = fill.isWriteData(io.imiss)
    val clear_tag = ctrl.st_0_tag | ctrl.do_hit_inv
    val tagv_wd:UInt = Mux(fill_tagv && !clear_tag,info.tagv,U(0))
    val data_wd:UInt = io.imiss.rdata
    val repl_mask = Range(0,cfg.n_way ).map(repl.way === U(_))
    val fill_wpos  = fill.getPos(info)
    val fill_wmsk = Range(0,cfg.n_bank).map(fill_wpos === U(_))
    val bank_rmsk = Range(0,cfg.n_bank).map(info.rpos === U(_))
    cfg.each_way(tagv(_)   .io.wd := tagv_wd)
    cfg.each_way(tagv(_)   .io.a  := addr   )
    cfg.each_blk(data(_)(_).io.wd := data_wd)
    cfg.each_blk(data(_)(_).io.a  := addr   )
    when(ctrl.init.valid){
        cfg.each_way(tagv(_).io.setWrite())
    }.elsewhen(ctrl.st_0_tag){
        cfg.each_way((i:Int)=>tagv(i).io.setWriteWhen(st_0_mask(i)))
    }.elsewhen(ctrl.do_hit_inv){
        cfg.each_way((i:Int)=>tagv(i).io.setWriteWhen(hits(i)))
    }.elsewhen(fill_tagv){
        cfg.each_way((i:Int)=>tagv(i).io.setWriteWhen(repl_mask(i)))
    }.elsewhen(ctrl.read){
        cfg.each_way((i:Int)=>tagv(i).io.setRead())
    }.otherwise{
        cfg.each_way(tagv(_).io.setIdle())
    }
    when(fill_data){
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setWriteWhen(repl_mask(i) && fill_wmsk(j))
        )
    }.elsewhen(ctrl.read){
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setReadWhen(bank_rmsk(j))
        )
    }.otherwise{
        cfg.each_blk((i:Int,j:Int)=>
            data(i)(j).io.setIdle()
        )
    }

    when(ctrl.read){
        repl.read(info.ridx)
    }
    when(ctrl.init.valid){
        repl.write(ctrl.init.idx,False)
    }.elsewhen(io.iresp.valid && io.iresp.allow){
        repl.write(info.widx,repl.update(fill.valid,hits))
    }
}
